/*******************************************************************/
/* @ All Rights Reserved Copyright (C) 2014 broada,Co.,Ltd.        */
/*                                                                 */
/*  NAME      = frcmd_client.c                                     */
/*  NOTE      =                                                    */
/*  DATE      = 2014/12/03 by guojq                                */
/*******************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <zmq.h>
#include <pthread.h>

#include "frcomm.h"
#include "frmem.h"
#include "client.pb-c.h"

#define CLIENT_B_VERSION         0x01
#define CLIENT_L_VERSION         0x01

#define CLIENT_DEFAULT_PORT    1108

#define CLIENT_MAX_LINE        1024
#define CMD_MAX_LEN            20
#define CMD_SETUP              "setup"
#define CMD_PRINT              "print"
#define CMD_EXPORT             "export"

#define MODEL_L7               "l7"
#define MODEL_FILTER           "filter"
#define MODEL_JAVA             "java"
#define MODEL_COMMIT           "commit"
#define MODEL_ROLLBACK         "rollback"
#define MODEL_EXIT             "exit"

#define FILTER_CMD_CLEAR       "clear"

//命令结束符
#define FRCMD_SERVER_END        "broada"

static int g_i_port = CLIENT_DEFAULT_PORT;
static char* g_p_server = "127.0.0.1";
static int g_i_srv_timeout = 3000;

static Frmemstat_mng g_st_memstat_mng={0,FR_MEMSTAT_COUNT_MAX};
static pthread_rwlock_t  g_memstat_rwlock = PTHREAD_RWLOCK_INITIALIZER;
static Frmem_env g_st_mem_env = {&g_st_memstat_mng, &g_memstat_rwlock};

/* 字符串转unsigned int */
#define ptoui(p) ((unsigned int) \
                 ((unsigned int)*((const unsigned char *)(p + 3)) << 24 | \
                 (unsigned int)*((const unsigned char *)(p + 2)) << 16 | \
                 (unsigned int)*((const unsigned char *)(p + 1)) << 8 | \
                 (unsigned int)*((const unsigned char *)(p + 0)) << 0))

static struct option lopts[] = {
    {"help", 0, NULL, 0},
    {"port", 1, NULL, 'p'},
    {"cmd", 1, NULL, 'c'},
    {"timeout", 1, NULL, 't'},
    {0, 0, 0, 0}
};

void frcmd_usage(void)
{
    printf("Usage: \n\t./client -h IP [-p PORT] [-t TIMEOUT] [-c CMDLINE]\n");
    printf("\t-h\tserver host\n");
    printf("\t\texample:\n");
    printf("\t\t\t-h 127.0.0.1\n");
    printf("\t-p\tserver port\n");
    printf("\t\texample:\n");
    printf("\t\t\t-p 1200(default:1108)\n");
    printf("\t-t\ttimeout\n");
    printf("\t\texample:\n");
    printf("\t\t\t-t 3(default:3) (second)\n");
    printf("\t-c\tcommand\n");
    printf("\t\tformat:\n");
    printf("\t\t\t-c \"setup module cmd path;\"\n");
    printf("\t\t\t-c \"setup mod cmd l7_proto path;\"\n");
    printf("\t\t\t-c \"setup l7 cmd ip_proto ip port-range l7_proto;\"\n");
    printf("\t\t\t-c \"setup l7_default cmd ip_proto l7_proto;\"\n");
    printf("\t\t\t-c \"setup filter cmd action [ip_proto] [src ip-range] [src port-range] [dst ip-range] [dst port-range];\"\n");
    printf("\t\t\t-c \"setup java family ipaddr port;\"\n");
    printf("\t\t\t-c \"setup lua add ipaddr ports lua_filename;\"\n");
    printf("\t\t\t-c \"setup lua del ipaddr ports;\"\n");
    printf("\t\t\t-c \"setup lua clear;\"\n");
    printf("\t\t\t-c \"setup ssl add ipaddr port cert passwd [domain www];\"\n");
    printf("\t\t\t-c \"setup ssl del ipaddr port [domain www];\"\n");
    printf("\t\t\t-c \"setup pcap_filter add \"filter\";\"\n");
    printf("\t\t\t-c \"setup pcap_filter clear;\"\n");
    printf("\t\t\t-c \"setup export ip time dumpfilename;\"\n");
    printf("\t\t\t-c \"commit cmd;\"\n");
    printf("\t\t\t-c \"print ct [L7_protocol];\"\n");
    printf("\t\t\t-c \"print system;\"\n");
    printf("\t\t\t-c \"print memory;\"\n");
    printf("\t\t\t-c \"print module stat/config;\"\n");
    printf("\t\t\t-c \"clearall;\"\n");
    printf("\t\t\t-c \"shutdown;\"\n");
    printf("\t\t\t-c \"quit;\"\n");
    printf("\t\texample:\n");
    printf("\t\t\t-c \"setup module add ../conf/modules/1521/module.xml;\"\n");
    printf("\t\t\t-c \"setup module del ../conf/modules/1521/module.xml;\"\n");
    printf("\t\t\t-c \"setup module clear;\"\n");
    printf("\t\t\t-c \"setup mod upload 80 80.mod;\"\n");
    printf("\t\t\t-c \"setup mod add 80;\"\n");
    printf("\t\t\t-c \"setup mod del 80;\"\n");
    printf("\t\t\t-c \"setup mod remove 80;\"\n");
    printf("\t\t\t-c \"setup mod clear;\"\n");
    printf("\t\t\t-c \"setup l7 add tcp 192.168.0.1 80 80;\"\n");
    printf("\t\t\t-c \"setup l7 del tcp 192.168.0.1 80;\"\n");
    printf("\t\t\t-c \"setup l7 add 0.0.0.0 0;\"\n");
    printf("\t\t\t-c \"setup l7 clear;\"\n");
    printf("\t\t\t-c \"setup l7_default add tcp oracle;\"\n");
    printf("\t\t\t-c \"setup l7_default del tcp;\"\n");
    printf("\t\t\t-c \"setup l7_default clear;\"\n");
    printf("\t\t\t-c \"setup filter add 1 tcp 0.0.0.0-0.0.0.0 0-0 192.168.0.1-192.168.0.20 80-100;\"\n");
    printf("\t\t\t-c \"setup filter del tcp 0.0.0.0-0.0.0.0 0-0 192.168.0.1-192.168.0.20 80-100;\"\n");
    printf("\t\t\t-c \"setup filter clear;\"\n");
    printf("\t\t\t-c \"setup java tcp 1108;\"\n");
    printf("\t\t\t-c \"setup report console start;\"\n");
    printf("\t\t\t-c \"setup report testtools start;\"\n");
    printf("\t\t\t-c \"setup report writefile start;\"\n");
    printf("\t\t\t-c \"setup report base64 start;\"\n");
    printf("\t\t\t-c \"setup report console stop;\"\n");
    printf("\t\t\t-c \"setup report testtools stop;\"\n");
    printf("\t\t\t-c \"setup report writefile stop;\"\n");
    printf("\t\t\t-c \"setup report base64 stop;\"\n");
    printf("\t\t\t-c \"setup lua add 192.168.0.1 80-80 ../conf/lua/tmp;\"\n");
    printf("\t\t\t-c \"setup lua del 192.168.0.1 80-80;\"\n");
    printf("\t\t\t-c \"setup lua clear;\"\n");
    printf("\t\t\t-c \"setup ssl add 192.168.0.1 443 ../conf/cert/server.key 123456;\"\n");
    printf("\t\t\t-c \"setup ssl add 192.168.0.1 443 ../conf/cert/server.key 123456 domain test;\"\n");
    printf("\t\t\t-c \"setup ssl del 192.168.0.1 443;\"\n");
    printf("\t\t\t-c \"setup ssl del 192.168.0.1 443 domain test;\"\n");
    printf("\t\t\t-c \"setup export 192.168.0.1 5 db2_2016_0108.pcap;\"\n");
    printf("\t\t\t-c \"commit module;\"\n");
    printf("\t\t\t-c \"commit mod;\"\n");
    printf("\t\t\t-c \"commit l7;\"\n");
    printf("\t\t\t-c \"commit l7_default;\"\n");
    printf("\t\t\t-c \"commit filter;\"\n");
    printf("\t\t\t-c \"commit lua;\"\n");
    printf("\t\t\t-c \"commit ssl;\"\n");
    printf("\t\t\t-c \"rollback filter;\"\n");
    printf("\t\t\t-c \"print system;\"\n");
    printf("\t\t\t-c \"print topology;\"\n");
    printf("\t\t\t-c \"print task;\"\n");
    printf("\t\t\t-c \"print ct [L7_protocol];\"\n");
    printf("\t\t\t-c \"print ct all;\"\n");
    printf("\t\t\t-c \"print ct unknown;\"\n");
    printf("\t\t\t-c \"print module stat;\"\n");
    printf("\t\t\t-c \"print module config;\"\n");
    printf("\t\t\t-c \"print server;\"\n");
    printf("\t\t\t-c \"print filter;\"\n");
    printf("\t\t\t-c \"print l7;\"\n");
    printf("\t\t\t-c \"print java;\"\n");
    printf("\t\t\t-c \"print memory;\"\n");
    printf("\t\t\t-c \"print version;\"\n");
    printf("\t\t\t-c \"print share_memory;\"\n");
    printf("\t\t\t-c \"export pcap ip:port;\"\n");
    printf("\t--help\n");
    printf("\n");
}


/*******************************************************************************
*  FUNC     :  处理应答数据
*  ARGS     :  p_response     应答的数据
*  RTN      :
*  NOTE     :
*******************************************************************************/
static void __frcmd_response(char *p_response)
{
    printf("%s", p_response);

    return;
}

static int __receive_unpack(char *p_data, int i_len)
{
    CommandResult *p_st_result = NULL;
    
    p_st_result = command_result__unpack(NULL, i_len, (const uint8_t *)p_data);
    if (p_st_result == NULL)
    {
        printf("recv err\n");
        return FR_FAIL;
    }
    
    if (p_st_result->status_code == COMMAND_RESULT__RESULT_TYPE__Type_OK)
        printf("CMD OK\n");
    else
        printf("CMD ERR, [%s]\n", p_st_result->reason);
    
    command_result__free_unpacked(p_st_result, NULL);
    
    return FR_SUCC;
}

static int __receive_data(void *socket, int i_flag_print)
{
    int i_cmd_len = 0;
    char *p_command = NULL;
    char arr_buf[1024];
    
    memset(arr_buf, 0x00, sizeof(arr_buf));
    
    while (1)
    {
        // Block until a message is available to be received from socket
        i_cmd_len = zmq_recv (socket, arr_buf, sizeof(arr_buf), 0);
        if (i_cmd_len == -1 || i_cmd_len == 0)
        {
            //超时退出
            //printf("timeout,err=[%s],ret=%d\n", strerror(errno), i_cmd_len);
            return FR_SUCC;
        }
        //保证收到的数据不超过用来接收的字符串，若超过，截断
        if (i_cmd_len >= sizeof(arr_buf))
            i_cmd_len = sizeof(arr_buf) - 1;
        arr_buf[i_cmd_len] = '\0';
        
        p_command = frmalloc(i_cmd_len + 1);
        if (p_command == NULL)
        {
            return FR_FAIL;
        }
        memcpy(p_command, arr_buf, i_cmd_len);
        p_command[i_cmd_len] = '\0';
        
        if (i_flag_print == 0)
            __receive_unpack(p_command, i_cmd_len);
        else
            printf("%s", p_command);
        frfree(p_command);
        if (!i_flag_print)
            break;
    }
    
    return FR_SUCC;
}
/*******************************************************************************
*  FUNC     :  接受服务端信息
*  ARGS     :  i_sockfd(socket句柄)
*  RTN      :  FR_SUCC (success)
*           :  FR_FAIL (failed)
*  NOTE     :
*******************************************************************************/
static int __receive_data_old(int i_sockfd)
{
    int i_nbytes = 0;
    int i_first_flag = 1;
    int i_copy = 0;
    int i_tmp_len = 0;      //计数拷贝了多少字节的长度字段
    unsigned int ui_data_len = 0;
    fd_set read_flags;
    struct timeval st_wait;
    char *p_tmp = NULL;
    char *p_now = NULL, *p_start = NULL;
    char arr_receive_buffer[CLIENT_MAX_LINE + 1];
    char arr_len[4] = {'\0'};

    while (1)
    {
        /* 接收来自服务器的响应信息，判断是否握手成功 */
        st_wait.tv_sec = 5;
        st_wait.tv_usec = 0;
        FD_ZERO(&read_flags);
        FD_SET(i_sockfd, &read_flags);

        if (select(i_sockfd + 1, &read_flags, 0, 0, &st_wait) <= 0)
            goto err;

        if(FD_ISSET(i_sockfd, &read_flags))
        {
            /* 接收来自服务器的响应信息 */
            i_nbytes = recv(i_sockfd, arr_receive_buffer, sizeof(arr_receive_buffer) - 1, 0);
            if (i_nbytes <= 0)
                goto err;
            p_tmp = arr_receive_buffer;
            
            while (i_nbytes)
            {
                /* 第一次接受到的数据前四位是该次响应数据的总长度 */
                if (i_first_flag == 1)
                {
                    //取长度 (可能长度分散在2次recv中，因此将长度先拷贝出来)
                    if (i_nbytes < 4 - i_tmp_len)
                    {
                        //下一次recv才能把长度收集全
                        memcpy(arr_len + i_tmp_len, p_tmp, i_nbytes);
                        i_tmp_len += i_nbytes;
                        break;
                    }
                    i_first_flag = 0;
                    memcpy(arr_len + i_tmp_len, p_tmp, 4 - i_tmp_len);
                    
                    //算出长度
                    ui_data_len = ptoui(arr_len);
                    
                    p_start = frmalloc(ui_data_len + 1);
                    if (p_start == NULL)
                        goto err;
                        
                    memset(p_start, 0x00, ui_data_len + 1);
                    p_now = p_start;
    
                    p_tmp += 4 - i_tmp_len;
                    i_nbytes -= 4 - i_tmp_len;
                    i_tmp_len = 0;
                }
                if (i_nbytes > ui_data_len)
                {
                    i_copy = ui_data_len;
                    i_nbytes -= ui_data_len;
                    ui_data_len = 0;
                }
                else
                {
                    i_copy = i_nbytes;
                    i_nbytes = 0;
                    ui_data_len -= i_copy;
                }
                
                memcpy(p_now, p_tmp, i_copy);
                p_now += i_copy;
                p_tmp += i_copy;
    
                if (ui_data_len == 0)
                {
                    //判断是否有结束符，有结束符说明命令结束，否则继续接收信息
                    if (strcasecmp(p_start, FRCMD_SERVER_END) == 0)
                    {
                        frfree(p_start);
                        return FR_SUCC;
                    }
                    __frcmd_response(p_start);
                    frfree(p_start);
                    p_start = NULL;
                    i_first_flag = 1;
                }
            }
        }
        // !FD_ISSET(i_sockfd, &read_flags)
        else
            goto err;
    }

err:
    if (p_start)
        frfree(p_start);
    return FR_FAIL;
}

/*******************************************************************************
*  FUNC     :  连接服务端
*  ARGS     :  p_addr(IP地址)
*           :  us_port(端口号)
*           :  p_sockfd(socket句柄)
*  RTN      :  FR_SUCC (success)
*           :  FR_FAIL (failed)
*  NOTE     :
*******************************************************************************/
static void *__init_socket(void *context
                           , const char *p_addr
                           , unsigned short us_port)
{
    void *socket = NULL;
    char arr_server[128];
    int i_timeout = g_i_srv_timeout;

    socket = zmq_socket(context, ZMQ_REQ);
    if (socket == NULL)
    {
        frwarn("zmq_socket failed, errno=[%s]", strerror(errno));
        return NULL;
    }
    if(zmq_setsockopt(socket, ZMQ_RCVTIMEO, &i_timeout, sizeof(i_timeout)) < 0)
    {
        frwarn("zmq_setsockopt failed, errno=[%s]", strerror(errno));
        zmq_close(socket);
        return NULL;
    }
    if(zmq_setsockopt(socket, ZMQ_SNDTIMEO, &i_timeout, sizeof(i_timeout)) < 0)
    {
        frwarn("zmq_setsockopt failed, errno=[%s]", strerror(errno));
        zmq_close(socket);
        return NULL;
    }
    if(zmq_setsockopt(socket, ZMQ_LINGER, &i_timeout, sizeof(i_timeout)) < 0)
    {
        frwarn("zmq_setsockopt failed, errno=[%s]", strerror(errno));
        zmq_close(socket);
        return NULL;
    }

    snprintf(arr_server, sizeof(arr_server), "tcp://%s:%d", p_addr, us_port);
    
    zmq_connect(socket, arr_server);
    
    return socket;
}

/*******************************************************************************
*  FUNC     :  发送消息给服务端
*  ARGS     :  i_sockfd         (socket句柄)
*           :  p_cmd            (发送的内容)
*  RTN      :  FR_SUCC          (success)
*           :  FR_FAIL          (failed)
*  NOTE     :
*******************************************************************************/
static int __send_data(void *socket, char *p_cmd, unsigned int ui_cmd_len)
{
    int i_ret = 0;
    
    if (p_cmd == NULL)
        return FR_FAIL;

    i_ret = zmq_send(socket, p_cmd, ui_cmd_len, 0);
    if (i_ret == -1)
        return FR_FAIL;

    return FR_SUCC;
}

/*******************************************************************************
*  FUNC     :  客户端和服务端之间的数据交互
*  ARGS     :  p_cmd        需要发送的数据
*           :  ui_cmdlen     数据长度
*  RTN      :  FR_SUCC (success)
*           :  FR_FAIL (failed)
*  NOTE     :
*******************************************************************************/
int frcmd_talking(char *p_cmd, unsigned int ui_cmdlen, int i_flag_print)
{
    int i_ret = 0;
    void *socket = 0;

    if (g_i_port <= 0 || g_i_port > 65535 || g_p_server == NULL || p_cmd == NULL || ui_cmdlen == 0)
    {
        frcmd_usage();
        return FR_FAIL;
    }
    
    void *context = zmq_init(1);
    if (context == NULL)
    {
        frwarn("zmq_ctx_new faild\n");
        return FR_FAIL;
    }

    /* 参数中读取IP地址和PORT */
    socket = __init_socket(context, g_p_server, (unsigned short)g_i_port);
    if (socket == NULL)
    {
        frwarn("connect to server faild\n");
        zmq_term(context);
        return FR_FAIL;
    }
    
    /* 发送数据 */
    i_ret = __send_data(socket, p_cmd, ui_cmdlen);
    if (i_ret != FR_SUCC)
        goto err;

    i_ret = __receive_data(socket, i_flag_print);
    if (i_ret != FR_SUCC)
    {
        frwarn("__receive_data failed\n");
        goto err;
    }
    zmq_close(socket);
    zmq_term(context);

    return FR_SUCC;
err:
    zmq_close(socket);
    zmq_term(context);
    return FR_FAIL;
}

/*******************************************************************************
*  FUNC     :  主程序
*  ARGS     :  argc(入力参数个数)
*           :  argv(入力参数)
*  RTN      :  0 (success)
*           :  1 (failed)
*  NOTE     :
*******************************************************************************/
int main(int argc, char *argv[])
{
    int i_opt_flag = 0, i_opt_index = 0, i_exit_flag = 1;
    int i_ret_fun = 0;

    if( fr_slab_init(&g_st_mem_env, "frcmd_client") == NULL)
    {
        frwarn("fr_slab_init err\n");
        return 1;
    }
    //获取程序参数
    while((i_opt_flag = getopt_long(argc,argv,"h:p:t:c:", lopts, &i_opt_index))!=-1)
    {
        switch(i_opt_flag)
        {
            case 'h':
                //frdbg("option h:[%s]\n",optarg);
                g_p_server = optarg;
                i_exit_flag = 0;
                break;
            case 'p':
                //frdbg("option p:[%s]\n",optarg);
                g_i_port = atoi(optarg);
                break;
            case 't':
                //frdbg("option t:[%s]\n",optarg);
                g_i_srv_timeout = atoi(optarg) * 1000;
                break;
            case 'c':
                yy_scan_string(optarg);
                yyparse();
                fr_slab_destroy();
                return 0;
            case 0:
                if (strcmp(lopts[i_opt_index].name, "help") == 0)
                {
                    frcmd_usage();
                    fr_slab_destroy();
                    return 1;
                }
                break;
            default:
                i_exit_flag = 1;
                frcmd_usage();
                fr_slab_destroy();
                return 1;
        }
    }

    if (i_exit_flag == 1)
  	{
  	    frcmd_usage();
  	    fr_slab_destroy();
        return 1;
    }

    yyparse();
    fr_slab_destroy();

    return 0;
}
